package com.ff.discounts.api


import com.ff.discounts.exception.ResponseSignException
import com.ff.discounts.library.log.HiLog
import com.ff.discounts.util.AESUtil
import com.ff.discounts.util.Constant
import com.ff.discounts.util.SHAUtil
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okio.Buffer
import org.apache.commons.lang3.StringUtils

import java.io.IOException
import java.nio.charset.Charset

/**
 * okhttp插件
 * 主要用来处理一些接口数据签名和加密功能
 */
class NetworkSecurityInterceptor : Interceptor {
    /**
     * 每个拦截器都会调用
     *
     * @param chain
     * @return
     * @throws IOException
     */
    override fun intercept(chain: Interceptor.Chain): Response {
        return try {
            //获取request
            var request = chain.request()

            //获取请求的url
            val url = request.url.toString()

            //请求方式
            val method = request.method

            //请求前处理网络请求
//            HiLog.d("request before:$method,$url")

            //获取请求体
            var requestBody = request.body
            if (url.endsWith("v2/addresses") && method == "POST") {
                //该接口需要添加参数签名

                //将请求体转为字符串
                val bodyString = getRequestBodyString(requestBody!!)

                //计算签名
                val sign = SHAUtil.sha256(bodyString)
                HiLog.d(
                    "sign params success:$method,$url,$sign"
                )

                //创建请求
                request = request.newBuilder() //添加原来的请求头
                    .headers(request.headers) //添加签名
                    .addHeader(Constant.HEADER_SIGN, sign) //设置请求方式和请求体
                    .method(method, requestBody)
                    .build()
            } else if (url.endsWith("v3/addresses") && method == "POST") {
                //该接口参数需要加密

                //将请求体转为字符串
                val bodyString = getRequestBodyString(requestBody!!)

                //将参数加密
                val encryptBodyString = AESUtil.encrypt(bodyString)

                //使用新参数创建请求体
                requestBody = encryptBodyString.toRequestBody("application/json".toMediaType())

                //创建请求
                request = request.newBuilder() //添加原来的请求头
                    .headers(request.headers) //设置请求方式和请求体
                    .method(method, requestBody)
                    .build()
                HiLog.d("encrypt param success:$method,$url")
            }

            //执行网络请求
            var response: Response = chain.proceed(request)

            //请求后处理网络请求
//            HiLog.d("request after:$method,$url")

            //获取网络响应签名值
            val sign = response.header(Constant.HEADER_SIGN)

            //判断是否有签名
            if (StringUtils.isNotBlank(sign)) {
                //获取响应字符串
                val dataString = getResponseString(response)

                //获取本地数据签名
                val localSign = SHAUtil.sha256(dataString)

                //判断签名
                if (localSign == sign) {
                    //签名正确

                    //什么也不做
                    HiLog.d("process sign correct:$method,$url")
                } else {
                    //签名错误
//                    Timber.e("process sign incorrect:$method,$url")
                    throw ResponseSignException()
                }
            } else {
                //没有签名

                //什么也不处理

                //真实项目中
                //如果所有接口响应有签名
                //如果判断没有签名
                //就直接抛出错误
//                HiLog.d("process not sign:$method,$url")
            }

            //判断响应解密接口
            if (url.endsWith("v3/addresses") && method == "GET") {
                var decryptString: String
                try {//该接口响应需要解密

                    //真实项目中可能所有接口都会加密
                    //所以就不用判断了

                    //我们这里为了降低课程难度
                    //所以只加密这个几个接口

                    //获取响应字符串
                    val dataString = getResponseString(response)

                    //解密数据
                    decryptString = AESUtil.decrypt(dataString)
                    //解密成功

                    //就认为数据没问题

                    //创建新的响应体
                    val responseBody =
                        decryptString.toResponseBody("application/json".toMediaType())

                    //创建响应
                    response = response.newBuilder()
                        .body(responseBody)
                        .headers(response.headers)
                        .build()
                    HiLog.d(
                        "process decrypt success:$method,$url"
                    )
                } catch (e: Exception) {
                    //解密失败
                    HiLog.d("process decrypt error:$method,$url")
                    throw NullPointerException()
                }
            }
            response
        } catch (e: IOException) {
            throw e
        }
    }

    /**
     * 将请求体转为字符串
     *
     * @param requestBody
     * @return
     */
    @Throws(IOException::class)
    private fun getRequestBodyString(requestBody: RequestBody): String {
        //创建缓冲
        val buffer = Buffer()

        //将请求体内容写入缓冲
        requestBody.writeTo(buffer)

        //转为字符串
        return buffer.readUtf8()
    }

    /**
     * 获取响应字符串
     *
     * @param response
     * @return
     */
    @Throws(IOException::class)
    private fun getResponseString(response: Response): String {
        //获取响应数据
        val responseBody = response.body

        //转为带缓冲的输入流
        val source = responseBody!!.source()

        //读取所有数据
        source.request(Long.MAX_VALUE)

        //获取缓冲器
        val buffer = source.buffer

        //将读取到的数据转为字符串
        return buffer.clone().readString(Charset.forName("UTF-8"))
    }
}
